🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / ble / src / androidMain / kotlin / org / meshtastic / core / ble / AndroidBluetoothRepository.kt
Displaying Raw • Download
core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBluetoothRepository.kt e85300531e124df08788c1b765d50af1bf6d516d (e8530053) Text, 9.26 KB
T8b949e/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.ble
Tff7b72import T7ee787android.Manifest
Tff7b72import T7ee787android.annotation.SuppressLint
Tff7b72import T7ee787android.bluetooth.BluetoothAdapter
Tff7b72import T7ee787android.bluetooth.BluetoothManager
Tff7b72import T7ee787android.content.Context
Tff7b72import T7ee787android.content.pm.PackageManager
Tff7b72import T7ee787android.os.Build
Tff7b72import T7ee787androidx.core.content.ContextCompat
Tff7b72import T7ee787androidx.lifecycle.Lifecycle
Tff7b72import T7ee787androidx.lifecycle.coroutineScope
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.suspendCancellableCoroutine
Tff7b72import T7ee787org.koin.core.annotation.Named
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787kotlin.coroutines.resume
T8b949e/** Android implementation of [BluetoothRepository]. */
Tf0883e@Single
Tff7b72class T56d364AndroidBluetoothRepositoryTb4b4b4(
Tff7b72private Tff7b72val Te6edf3contextTb4b4b4: Te6edf3ContextTb4b4b4,
Tff7b72private Tff7b72val Te6edf3dispatchersTb4b4b4: Te6edf3CoroutineDispatchersTb4b4b4,
Tf0883e@NamedTb4b4b4(Ta5d6ff"Ta5d6ffProcessLifecycleTa5d6ff"Tb4b4b4) Tff7b72private Tff7b72val Te6edf3processLifecycleTb4b4b4: Te6edf3LifecycleTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3BluetoothRepository Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3bluetoothManager Tff7b72= Te6edf3contextTb4b4b4.Te6edf3getSystemServiceTb4b4b4(Te6edf3ContextTb4b4b4.Te6edf3BLUETOOTH_SERVICETb4b4b4) Tff7b72as Te6edf3BluetoothManager
Tff7b72private Tff7b72val Te6edf3bluetoothAdapterTb4b4b4: Te6edf3BluetoothAdapter? Tff7b72= Te6edf3bluetoothManagerTb4b4b4.Te6edf3adapter
Tff7b72private Tff7b72val Te6edf3_state Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3BluetoothStateTb4b4b4(Te6edf3hasPermissions Tff7b72= Te6edf3hasBluetoothPermissionsTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72override Tff7b72val Te6edf3stateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3BluetoothStateTff7b72> Tff7b72= Te6edf3_stateTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3deviceCache Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657StringTb4b4b4, Te6edf3MeshtasticBleDeviceTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72init Tb4b4b4{
Te6edf3processLifecycleTb4b4b4.Te6edf3coroutineScopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3defaultTb4b4b4) Tb4b4b4{ Te6edf3updateBluetoothStateTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhasBluetoothPermissionsTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Tff7b72if Tb4b4b4(Te6edf3BuildTb4b4b4.Te6edf3VERSIONTb4b4b4.Te6edf3SDK_INT Tff7b72>Tff7b72= Te6edf3BuildTb4b4b4.Te6edf3VERSION_CODESTb4b4b4.Te6edf3STb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3hasConnect Tff7b72=
Te6edf3ContextCompatTb4b4b4.Te6edf3checkSelfPermissionTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4) Tff7b72=Tff7b72=
Te6edf3PackageManagerTb4b4b4.Te6edf3PERMISSION_GRANTED
Tff7b72val Te6edf3hasScan Tff7b72=
Te6edf3ContextCompatTb4b4b4.Te6edf3checkSelfPermissionTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_SCANTb4b4b4) Tff7b72=Tff7b72=
Te6edf3PackageManagerTb4b4b4.Te6edf3PERMISSION_GRANTED
Te6edf3hasConnect Tff7b72&Tff7b72& Te6edf3hasScan
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Pre-Android 12: classic Bluetooth permissions are install-time.
Tff7b72true
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffrefreshStateTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3processLifecycleTb4b4b4.Te6edf3coroutineScopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3defaultTb4b4b4) Tb4b4b4{ Te6edf3updateBluetoothStateTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffisValidTb4b4b4(Te6edf3bleAddressTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3BluetoothAdapterTb4b4b4.Te6edf3checkBluetoothAddressTb4b4b4(Te6edf3bleAddressTb4b4b4)
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionThrownTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffSwallowedExceptionTa5d6ff"Tb4b4b4)
Tf0883e@SuppressLintTb4b4b4(Ta5d6ff"Ta5d6ffMissingPermissionTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffbondTb4b4b4(Te6edf3deviceTb4b4b4: Te6edf3BleDeviceTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3macAddress Tff7b72= Te6edf3deviceTb4b4b4.Te6edf3address
Tff7b72val Te6edf3remoteDevice Tff7b72=
Te6edf3bluetoothAdapterTff7b72?.Te6edf3getRemoteDeviceTb4b4b4(Te6edf3macAddressTb4b4b4) Tff7b72?: Tff7b72throw Te6edf3ExceptionTb4b4b4(Ta5d6ff"Ta5d6ffBluetooth adapter unavailableTa5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3remoteDeviceTb4b4b4.Te6edf3bondState Tff7b72=Tff7b72= Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3BOND_BONDEDTb4b4b4) Tb4b4b4{
Te6edf3updateBluetoothStateTb4b4b4(Tb4b4b4)
Tff7b72return
Tb4b4b4}
Te6edf3suspendCancellableCoroutineTff7b72<Tffa657UnitTff7b72> Tb4b4b4{ Te6edf3cont Tff7b72-Tff7b72>
Tff7b72val Te6edf3receiver Tff7b72=
Tff7b72object Tb4b4b4: T56d364androidTb4b4b4.Te6edf3contentTb4b4b4.Te6edf3BroadcastReceiverTb4b4b4(Tb4b4b4) Tb4b4b4{
Tf0883e@SuppressLintTb4b4b4(Ta5d6ff"Ta5d6ffMissingPermissionTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffonReceiveTb4b4b4(Te6edf3cTb4b4b4: Te6edf3ContextTb4b4b4, Te6edf3intentTb4b4b4: Te6edf3androidTb4b4b4.Te6edf3contentTb4b4b4.Te6edf3IntentTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3intentTb4b4b4.Te6edf3action Tff7b72=Tff7b72= Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3ACTION_BOND_STATE_CHANGEDTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3d Tff7b72=
Te6edf3androidxTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3contentTb4b4b4.Te6edf3IntentCompatTb4b4b4.Te6edf3getParcelableExtraTb4b4b4(
Te6edf3intentTb4b4b4,
Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3EXTRA_DEVICETb4b4b4,
Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTff7b72::Te6edf3classTb4b4b4.Te6edf3javaTb4b4b4,
Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3dTff7b72?.Te6edf3addressTff7b72?.Te6edf3equalsTb4b4b4(Te6edf3macAddressTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4) Tff7b72=Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3state Tff7b72=
Te6edf3intentTb4b4b4.Te6edf3getIntExtraTb4b4b4(
Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3EXTRA_BOND_STATETb4b4b4,
Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3ERRORTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3prevState Tff7b72=
Te6edf3intentTb4b4b4.Te6edf3getIntExtraTb4b4b4(
Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3EXTRA_PREVIOUS_BOND_STATETb4b4b4,
Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3ERRORTb4b4b4,
Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3state Tff7b72=Tff7b72= Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3BOND_BONDEDTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3contextTb4b4b4.Te6edf3unregisterReceiverTb4b4b4(Tff7b72thisTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3ignoredTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3contTb4b4b4.Te6edf3isActiveTb4b4b4) Te6edf3contTb4b4b4.Te6edf3resumeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(
Te6edf3state Tff7b72=Tff7b72= Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3BOND_NONE Tff7b72&Tff7b72&
Te6edf3prevState Tff7b72=Tff7b72= Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3BOND_BONDING
Tb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3contextTb4b4b4.Te6edf3unregisterReceiverTb4b4b4(Tff7b72thisTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3ignoredTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3contTb4b4b4.Te6edf3isActiveTb4b4b4) Tb4b4b4{
Te6edf3contTb4b4b4.Te6edf3resumeWithTb4b4b4(Te6edf3ResultTb4b4b4.Te6edf3failureTb4b4b4(Te6edf3ExceptionTb4b4b4(Ta5d6ff"Ta5d6ffBonding failed or rejectedTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72val Te6edf3filter Tff7b72= Te6edf3androidTb4b4b4.Te6edf3contentTb4b4b4.Te6edf3IntentFilterTb4b4b4(Te6edf3androidTb4b4b4.Te6edf3bluetoothTb4b4b4.Te6edf3BluetoothDeviceTb4b4b4.Te6edf3ACTION_BOND_STATE_CHANGEDTb4b4b4)
Te6edf3ContextCompatTb4b4b4.Te6edf3registerReceiverTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3receiverTb4b4b4, Te6edf3filterTb4b4b4, Te6edf3ContextCompatTb4b4b4.Te6edf3RECEIVER_NOT_EXPORTEDTb4b4b4)
Te6edf3contTb4b4b4.Te6edf3invokeOnCancellation Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3contextTb4b4b4.Te6edf3unregisterReceiverTb4b4b4(Te6edf3receiverTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3ignoredTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3remoteDeviceTb4b4b4.Te6edf3createBondTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3contextTb4b4b4.Te6edf3unregisterReceiverTb4b4b4(Te6edf3receiverTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3ignoredTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3contTb4b4b4.Te6edf3isActiveTb4b4b4) Te6edf3contTb4b4b4.Te6edf3resumeWithTb4b4b4(Te6edf3ResultTb4b4b4.Te6edf3failureTb4b4b4(Te6edf3ExceptionTb4b4b4(Ta5d6ff"Ta5d6ffFailed to initiate bondingTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3updateBluetoothStateTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72internal Tff7b72suspend Tff7b72fun Td2a8ffupdateBluetoothStateTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3enabled Tff7b72= Te6edf3bluetoothAdapterTff7b72?.Te6edf3isEnabled Tff7b72=Tff7b72= Tff7b72true
Tff7b72var Te6edf3hasPermissions Tff7b72= Te6edf3hasBluetoothPermissionsTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3bondedDevices Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3hasPermissionsTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3getBondedAppPeripheralsTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3SecurityExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffSecurityException accessing bonded devices. Missing BLUETOOTH_CONNECT?Ta5d6ff" Tb4b4b4}
Te6edf3hasPermissions Tff7b72= Tff7b72false
Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3newState Tff7b72= Te6edf3BluetoothStateTb4b4b4(Te6edf3hasPermissions Tff7b72= Te6edf3hasPermissionsTb4b4b4, Te6edf3enabled Tff7b72= Te6edf3enabledTb4b4b4, Te6edf3bondedDevices Tff7b72= Te6edf3bondedDevicesTb4b4b4)
Te6edf3_stateTb4b4b4.Te6edf3emitTb4b4b4(Te6edf3newStateTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDetected our bluetooth access=Tffd700$Te6edf3newStateTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressLintTb4b4b4(Ta5d6ff"Ta5d6ffMissingPermissionTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffgetBondedAppPeripheralsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3BleDeviceTff7b72> Tb4b4b4{
Tff7b72val Te6edf3bonded Tff7b72= Te6edf3bluetoothAdapterTff7b72?.Te6edf3bondedDevices Tff7b72?: Tff7b72return Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3bondedAddresses Tff7b72= Te6edf3bondedTb4b4b4.Te6edf3mapToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3address Tb4b4b4}
T8b949e// Evict entries for devices that are no longer bonded and update names in case the
T8b949e// user renamed the device in firmware since the cache was populated.
Te6edf3deviceCacheTb4b4b4.Te6edf3keysTb4b4b4.Te6edf3retainAllTb4b4b4(Te6edf3bondedAddressesTb4b4b4)
Tff7b72return Te6edf3bondedTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3device Tff7b72-Tff7b72>
Tff7b72val Te6edf3cached Tff7b72= Te6edf3deviceCacheTb4b4b4.Te6edf3getOrPutTb4b4b4(Te6edf3deviceTb4b4b4.Te6edf3addressTb4b4b4) Tb4b4b4{ Te6edf3MeshtasticBleDeviceTb4b4b4(Te6edf3deviceTb4b4b4.Te6edf3addressTb4b4b4, Te6edf3deviceTb4b4b4.Te6edf3nameTb4b4b4) Tb4b4b4}
T8b949e// If the name changed (firmware rename, etc.), replace the cached entry and return the new one.
Tff7b72if Tb4b4b4(Te6edf3cachedTb4b4b4.Te6edf3name Tff7b72!Tff7b72= Te6edf3deviceTb4b4b4.Te6edf3nameTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3updated Tff7b72= Te6edf3MeshtasticBleDeviceTb4b4b4(Te6edf3deviceTb4b4b4.Te6edf3addressTb4b4b4, Te6edf3deviceTb4b4b4.Te6edf3nameTb4b4b4)
Te6edf3deviceCacheTff7b72[Te6edf3deviceTb4b4b4.Te6edf3addressTff7b72] Tff7b72= Te6edf3updated
Te6edf3updated
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3cached
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressLintTb4b4b4(Ta5d6ff"Ta5d6ffMissingPermissionTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffisBondedTb4b4b4(Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3bluetoothAdapterTff7b72?.Te6edf3bondedDevicesTff7b72?.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3addressTb4b4b4.Te6edf3equalsTb4b4b4(Te6edf3addressTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4} Tff7b72?: Tff7b72false
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3SecurityExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffSecurityException checking bonded devices. Missing BLUETOOTH_CONNECT?Ta5d6ff" Tb4b4b4}
Tff7b72false
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.07s